﻿#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the Xcoordination Application Space
 * ("XcoAppSpace") http://www.xcoordination.com
 *
 * Copyright (C) 2009 Xcoordination http://www.xcoordination.com
 *
 * XcoAppSpace is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General
 * Public License as published by the Free Software Foundation.
 *
 * XcoAppSpace is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 * or write to the Free Software Foundation, Inc., 51 Franklin
 * Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ---------------------------------------------------------------- */
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Ccr.Core;
using XcoAppSpaces.Contracts.CcrWiring;
using XcoAppSpaces.Contracts.Communication;
using XcoAppSpaces.Contracts.Logging;
using XcoAppSpaces.Contracts.Security;
using XcoAppSpaces.Contracts.Service;
using System.Net;
using XcoAppSpaces.Contracts.Worker;
using XcoAppSpaces.Core.Configuration;
using XcoAppSpaces.Contracts.CcrDispatching;

namespace XcoAppSpaces.Core
{
    /// <summary>
	/// The main class of XcoAppSpaces. XcoAppSpace represents the application space
	/// and allows running workers, as well as resolving worker that have already been
	/// started and connecting to workers running at a remote space. It also provides
	/// basic dependency injection container functionality.
	/// </summary>
	public class XcoAppSpace : IXcoAppSpace
    {
    	private readonly IXcoServiceRegistry serviceReg;
        private readonly ICcrDispatcherService dispatcherService;
		private readonly ICcrWirer ccrWirer;
		private readonly IConnector connector;
		private readonly IXcoLogger log;
		private readonly LocalWorkerRegistry localWorkers;

		/// <summary>
		/// Provides meta information about the XcoAppSpace instance, e.g. information about 
		/// running workers and communication services.
		/// </summary>
		public XcoSpaceInfo Info { get; private set; }
		
		/// <summary>
		/// The credentials that are used by the space to connect to remote workers.
		/// </summary>
		public ICredentials Credentials { get; set; }
		
		/// <summary>
		/// Creates a new XcoAppSpace without remote communication functionality.
		/// </summary>
        public XcoAppSpace() : this(new XcoAppSpaceConfig()) { }
        
		/// <summary>
		/// Creates a new XcoAppSpace by using the given config string.
		/// </summary>
		/// <param name="configString">The config string to configure the appspace instance.</param>
		public XcoAppSpace(string configString) : this(new XcoFluentAppSpaceConfig(configString)) { }

		/// <summary>
		/// Creates a new XcoAppSpace by using the given configuration.
		/// </summary>
		/// <param name="config"></param>
		public XcoAppSpace(IXcoConfiguration config)
        {
			//create service registry and fill it with given configuration
        	this.serviceReg = new ServiceRegistry(this);
			config.InitServiceRegistry(serviceReg);
			this.localWorkers = new LocalWorkerRegistry();
			serviceReg.Register<IXcoWorkerRegistry>(localWorkers);
            this.ccrWirer = serviceReg.Resolve<ICcrWirer>();
			this.dispatcherService = serviceReg.Resolve<ICcrDispatcherService>();
			this.connector = serviceReg.Resolve<IConnector>();
			this.log = XcoLoggerFactory.CreateInstance(this.GetType());
			
			log.Debug("XcoAppSpace starting up...");
			serviceReg.InitializeServices();
			serviceReg.StartServices();

			if (connector.LocalAddress != null)
				log.Info("XcoAppSpace initialized, running on " + this.connector.LocalAddress + ".");
			else //space is running without remote communication
				log.Info("XcoAppSpace initialized, running without remote communication.");

			this.Info = new XcoSpaceInfo(localWorkers.Workers, connector.CommServices, serviceReg);
		}

		/// <summary>
		/// The CCR dispatcher that is used by the appspace.
		/// </summary>
        public Dispatcher DefaultDispatcher
        {
            get { return this.dispatcherService.Dispatcher; }
        }

		/// <summary>
		/// The CCR dispatcher queue that is used by the appspace.
		/// </summary>
        public DispatcherQueue DefaultDispatcherQueue
        {
			get { return this.dispatcherService.DispatcherQueue; }
        }

		/// <summary>
		/// The address of the space (that can be used by other spaces to connect to this space)
		/// </summary>
		public string Address
		{
			get{ return this.connector.LocalAddress; }
		}

		/// <summary>
		/// Port where errors are posted that occur during commmunication with other space instances
		/// and that have not been caught by causalities (e.g. errors during message deserialization 
		/// or security errors).
		/// </summary>
		public Port<XcoCommunicationError> Errors
		{
			get { return connector.Errors; }
		}

        #region Worker Hosting
		/// <summary>
		/// Resolves an instance of the requested type with the given name from the local appspace.
		/// </summary>
		/// <typeparam name="TContract">The type that is requested.</typeparam>
		/// <returns>The resolved object.</returns>
		public TContract Resolve<TContract>()
		{
			return Resolve<TContract>(null);
		}

		/// <summary>
		/// Resolves worker of the requested type with the given name from the local appspace.
		/// </summary>
		/// <typeparam name="TContract">The type that is requested.</typeparam>
		/// <param name="name">The name of the object to retreive.</param>
		/// <returns>The resolved object.</returns>
		public TContract Resolve<TContract>(string name)
		{
			return this.localWorkers.Resolve<TContract>(name);
		}

		/// <summary>
		/// Runs a worker within the appspace. The worker is published so other appspace instances can connect to it.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker, which must be an IPort (meaning it must be a CCR Port or PortSet).</typeparam>
		/// <typeparam name="TImplementation">The implementation type of the worker (which must inherit from the contract type).</typeparam>
		/// <returns>The worker instance that has been created.</returns>
        public TMessageContract RunWorker<TMessageContract, TImplementation>() 
                                            where TMessageContract : IPort
                                            where TImplementation : TMessageContract, new()
        {
            return RunWorker<TMessageContract, TImplementation>(PublishMode.Published, null, null);
        }

		/// <summary>
		/// Runs a worker within the appspace. The worker is published so other appspace instances can connect to it.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker, which must be an IPort (meaning it must be a CCR Port or PortSet).</typeparam>
		/// <typeparam name="TImplementation">The implementation type of the worker (which must inherit from the contract type).</typeparam>
		/// <param name="name">The name under which the worker should be registered.</param>
		/// <returns>The worker instance that has been created.</returns>
		public TMessageContract RunWorker<TMessageContract, TImplementation>(string name)
                                            where TMessageContract : IPort
                                            where TImplementation : TMessageContract, new()
        {
            return RunWorker<TMessageContract, TImplementation>(PublishMode.Published, name, null);
        }

		/// <summary>
		/// Runs a worker within the appspace.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker, which must be an IPort (meaning it must be a CCR Port or PortSet).</typeparam>
		/// <typeparam name="TImplementation">The implementation type of the worker (which must inherit from the contract type).</typeparam>
		/// <param name="publish">Defines if the worker should be published so other appspace instances can connnect to it.</param>
		/// <returns>The worker instance that has been created.</returns>
		public TMessageContract RunWorker<TMessageContract, TImplementation>(PublishMode publish)
			where TMessageContract : IPort
			where TImplementation : TMessageContract, new()
		{
			return RunWorker<TMessageContract, TImplementation>(publish, null, null);
		}

		/// <summary>
		/// Runs a worker within the appspace.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker, which must be an IPort (meaning it must be a CCR Port or PortSet).</typeparam>
		/// <typeparam name="TImplementation">The implementation type of the worker (which must inherit from the contract type).</typeparam>
		/// <param name="publish">Defines if the worker should be published so other appspace instances can connnect to it.</param>
		/// <param name="name">The name under which the worker should be registered.</param>
		/// <returns>The worker instance that has been created.</returns>
        public TMessageContract RunWorker<TMessageContract, TImplementation>(PublishMode publish, string name)
			where TMessageContract : IPort
			where TImplementation : TMessageContract, new()
		{
			return RunWorker<TMessageContract, TImplementation>(publish, name, null);
		}

		/// <summary>
		/// Runs a worker within the appspace.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker, which must be an IPort (meaning it must be a CCR Port or PortSet).</typeparam>
		/// <typeparam name="TImplementation">The implementation type of the worker (which must inherit from the contract type).</typeparam>
		/// <param name="publishMode">Defines if the worker should be published so other appspace instances can connnect to it.</param>
		/// <param name="name">The name under which the worker should be registered.</param>
		/// <param name="queue">The CCR dispatcher queue that should be used, or null if the appspace's default dispatcher queue should be used.</param>
		/// <returns>The worker instance that has been created.</returns>
        public TMessageContract RunWorker<TMessageContract, TImplementation>(PublishMode publishMode, string name, DispatcherQueue queue)
                                            where TMessageContract : IPort
                                            where TImplementation : TMessageContract, new()
        {
            var worker = this.ccrWirer.CreateWorker<TMessageContract, TImplementation>(queue);
			localWorkers.AddWorker<TMessageContract>(worker, name, publishMode);
			log.Info("Running new worker: Contract = " + typeof(TMessageContract) + ", Implementation = " + typeof(TImplementation) + (name != null ? (", Name = " + name) : ""));
            return worker.Instance;
        }

		/// <summary>
		/// Runs a worker within the appspace.  The worker is published so other appspace instances can connect to it.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker, which must be an IPort (meaning it must be a CCR Port or PortSet).</typeparam>
		/// <returns>The worker instance that has been created.</returns>
		public TMessageContract RunWorker<TMessageContract>(TMessageContract worker)
			where TMessageContract : IPort
		{
            return RunWorker<TMessageContract>(worker, PublishMode.Published, null, null);
		}

		/// <summary>
		/// Runs a worker within the appspace.  The worker is published so other appspace instances can connect to it.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker, which must be an IPort (meaning it must be a CCR Port or PortSet).</typeparam>
		/// <param name="worker">The worker instance to run.</param>
		/// <param name="name">The name under which the worker should be registered.</param>
		/// <returns>The worker instance that has been created.</returns>
		public TMessageContract RunWorker<TMessageContract>(TMessageContract worker, string name)
			where TMessageContract : IPort
		{
            return RunWorker<TMessageContract>(worker, PublishMode.Published, name, null);
		}

		/// <summary>
		/// Runs a worker within the appspace. 
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker, which must be an IPort (meaning it must be a CCR Port or PortSet).</typeparam>
		/// <param name="worker">The worker instance to run.</param>
		/// <param name="publish">Defines if the worker should be published so other appspace instances can connnect to it.</param>
		/// <returns>The worker instance that has been created.</returns>
		public TMessageContract RunWorker<TMessageContract>(TMessageContract worker, PublishMode publish)
			where TMessageContract : IPort
		{
			return RunWorker<TMessageContract>(worker, publish, null, null);
		}

		/// <summary>
		/// Runs a worker within the appspace.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker, which must be an IPort (meaning it must be a CCR Port or PortSet).</typeparam>
		/// <param name="worker">The worker instance to run.</param>
		/// <param name="publish">Defines if the worker should be published so other appspace instances can connnect to it.</param>
		/// <param name="name">The name under which the worker should be registered.</param>
		/// <returns>The worker instance that has been created.</returns>
		public TMessageContract RunWorker<TMessageContract>(TMessageContract worker,PublishMode publish, string name)
			where TMessageContract : IPort
		{
			return RunWorker<TMessageContract>(worker, publish, name, null);
		}

		/// <summary>
		/// Runs a worker within the appspace.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker, which must be an IPort (meaning it must be a CCR Port or PortSet).</typeparam>
		/// <param name="worker">The worker instance to run.</param>
		/// <param name="publish">Defines if the worker should be published so other appspace instances can connnect to it.</param>
		/// <param name="name">The name under which the worker should be registered.</param>
		/// <param name="queue">The CCR dispatcher queue that should be used, or null if the appspace's default dispatcher queue should be used.</param>
		/// <returns>The worker instance that has been created.</returns>
        public TMessageContract RunWorker<TMessageContract>(TMessageContract worker, PublishMode publish, string name, DispatcherQueue queue)
			where TMessageContract : IPort
		{
			var workerInfo = this.ccrWirer.WireWorker<TMessageContract>(worker, queue);
			localWorkers.AddWorker<TMessageContract>(workerInfo, name, publish);
			log.Info("Running new worker: Contract = " + typeof(TMessageContract) + (name != null ? (", Name = " + name) : ""));
			return workerInfo.Instance;
		}

		/// <summary>
		/// Disposes the given worker instance and removes it from the appspace.
		/// </summary>
		/// <param name="worker">The worker to be removed.</param>
		public void StopWorker(IPort worker)
		{
			localWorkers.Remove(worker);
			ccrWirer.UnwireWorker(worker);
		}

		/// <summary>
		/// Disposes a worker instance and removes it from the appspace.
		/// </summary>
		/// <typeparam name="TMessageContract">The type of the worker.</typeparam>
		public void StopWorker<TMessageContract>()
			where TMessageContract : IPort
		{
			StopWorker<TMessageContract>(null);
		}

		/// <summary>
		/// Disposes a worker instance and removes it from the appspace.
		/// </summary>
		/// <typeparam name="TMessageContract">The type of the worker.</typeparam>
		/// <param name="name">The name of the worker.</param>
		public void StopWorker<TMessageContract>(string name)
			where TMessageContract : IPort
		{
			var worker = this.Resolve<TMessageContract>(name);
			if (worker != null)
				StopWorker(worker);
		}

        #endregion

        #region Remote Worker
		/// <summary>
		/// Establishes a connection to a worker that is running at a remote space, and registers
		/// the worker at the local space.  After ConnectWorker is called, the worker to which the
		/// connection has been established is also available through the appspace's Resolve method.
		/// This method allows using addresses that are not displayable with a "hostname:port" pattern.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker.</typeparam>
		/// <param name="workerAddress">The address of the worker in form "address/workername"</param>
		/// <returns>A worker contract instance that allows to post messages to the remote worker.</returns>
		public TMessageContract ConnectWorker<TMessageContract>(string workerAddress)
											where TMessageContract : IPort, new()
		{
			return ConnectWorker<TMessageContract>(PublishMode.Unpublished, null, workerAddress, null);
		}

		/// <summary>
		/// Establishes a connection to a worker that is running at a remote space, and registers
		/// the worker at the local space. After ConnectWorker is called, the worker to which the
		/// connection has been established is also available through the appspace's Resolve method.
		/// This method allows using addresses that are not displayable with a "hostname:port" pattern.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker.</typeparam>
		/// <param name="workerAddress">The address of the worker in form "address/workername"</param>
		/// <param name="localName">The name under which the worker should be registered locally.</param>
		/// <returns>A worker contract instance that allows to post messages to the remote worker.</returns>
		public TMessageContract ConnectWorker<TMessageContract>(string workerAddress, string localName)
											where TMessageContract : IPort, new()
		{
            return ConnectWorker<TMessageContract>(PublishMode.Unpublished, null, workerAddress, localName);
		}

		/// <summary>
		/// Establishes a connection to a worker that is running at a remote space, and registers
		/// the worker at the local space. After ConnectWorker is called, the worker to which the
		/// connection has been established is also available through the appspace's Resolve method.
		/// This method allows using addresses that are not displayable with a "hostname:port" pattern.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker.</typeparam>
		/// <param name="commServiceName">The name of the communication service that should be used for communication with the worker.</param>
		/// <param name="workerAddress">The address of the worker in form "address/workername"</param>
		/// <param name="localName">The name under which the worker should be registered locally.</param>
		/// <returns>A worker contract instance that allows to post messages to the remote worker.</returns>
		public TMessageContract ConnectWorker<TMessageContract>(string commServiceName, string workerAddress, string localName)
											where TMessageContract : IPort, new()
		{
            return ConnectWorker<TMessageContract>(PublishMode.Unpublished, commServiceName, workerAddress, localName);
		}

		/// <summary>
		/// Establishes a connection to a worker that is running at a remote space, and registers
		/// the worker at the local space.  After ConnectWorker is called, the worker to which the
		/// connection has been established is also available through the appspace's Resolve method.
		/// This method allows using addresses that are not displayable with a "hostname:port" pattern.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker.</typeparam>
		/// <param name="publish">Defines if the created worker proxy should be published so other appspace instances can connnect to it, and by that use the appspace instance to connect to the original worker.</param>
		/// <param name="workerAddress">The address of the worker in form "address/workername"</param>
		/// <returns>A worker contract instance that allows to post messages to the remote worker.</returns>
		public TMessageContract ConnectWorker<TMessageContract>(PublishMode publish, string workerAddress)
											where TMessageContract : IPort, new()
		{
			return ConnectWorker<TMessageContract>(publish, null, workerAddress, null);
		}

		/// <summary>
		/// Establishes a connection to a worker that is running at a remote space, and registers
		/// the worker at the local space. After ConnectWorker is called, the worker to which the
		/// connection has been established is also available through the appspace's Resolve method.
		/// This method allows using addresses that are not displayable with a "hostname:port" pattern.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker.</typeparam>
		/// <param name="publish">Defines if the created worker proxy should be published so other appspace instances can connnect to it, and by that use the appspace instance to connect to the original worker.</param>
		/// <param name="workerAddress">The address of the worker in form "address/workername"</param>
		/// <param name="localName">The name under which the worker should be registered locally.</param>
		/// <returns>A worker contract instance that allows to post messages to the remote worker.</returns>
        public TMessageContract ConnectWorker<TMessageContract>(PublishMode publish, string workerAddress, string localName)
											where TMessageContract : IPort, new()
		{
			return ConnectWorker<TMessageContract>(publish, null, workerAddress, localName);
		}

		/// <summary>
		/// Establishes a connection to a worker that is running at a remote space, and registers
		/// the worker at the local space. After ConnectWorker is called, the worker to which the
		/// connection has been established is also available through the appspace's Resolve method.
		/// This method allows using addresses that are not displayable with a "hostname:port" pattern.
		/// </summary>
		/// <typeparam name="TMessageContract">The contract type of the worker.</typeparam>
		/// <param name="publishMode">Defines if the created worker proxy should be published so other appspace instances can connnect to it, and by that use the appspace instance to connect to the original worker.</param>
		/// <param name="commServiceName">The name of the communication service that should be used for communication with the worker.</param>
		/// <param name="workerAddress">The address of the worker in form "address/workername"</param>
		/// <param name="localName">The name under which the worker should be registered locally.</param>
		/// <returns>A worker contract instance that allows to post messages to the remote worker.</returns>
		public TMessageContract ConnectWorker<TMessageContract>(PublishMode publishMode, string commServiceName, string workerAddress, string localName)
											where TMessageContract : IPort, new()
		{
			var workerProxyInfo = this.connector.ConnectWorker<TMessageContract>(workerAddress, commServiceName, this.Credentials);
			var workerProxy = this.ccrWirer.CreateWorkerProxy<TMessageContract>(workerProxyInfo);
			localWorkers.AddWorkerProxy<TMessageContract>(workerProxy, localName, publishMode, workerAddress, workerProxyInfo);
			log.Info("Connected to worker: Address = " + workerAddress + ", Contract = " + typeof(TMessageContract) + (localName != null ? (", Local Name = " + localName) : ""));
			return workerProxy;
		}
        #endregion

        #region IDisposable Members
		private bool disposing = false;
		
		/// <summary>
		/// Closes the appspace and disposes the CCR dispatcher if it was not handed in at startup.
		/// </summary>
		public void Dispose()
        {
			if (!this.disposing)
			{
				this.disposing = true;
				this.serviceReg.Dispose();
				log.Info("XcoAppSpace shut down.");
                this.disposing = false;
			}
        }
        #endregion

		#region Static Setup Methods

    	/// <summary>
    	/// Returns an XcoAppSpaceConfig object for configuring and creating a new space instance.
    	/// </summary>
		public static XcoFluentAppSpaceConfig Configure
    	{
    		get{return new XcoFluentAppSpaceConfig();}
    	}

		/// <summary>
		/// Initializes the XcoAppSpace using the given config string.
		/// </summary>
		/// <param name="configString">The config string that should be used for initialization of the space.</param>
		/// <returns>An XcoAppSpaceConfiguration instance for further configuration of the space.</returns>
		public static XcoFluentAppSpaceConfig ConfigureWithConfigString(string configString)
		{
			return new XcoFluentAppSpaceConfig(configString);
		}

    	#endregion
	}
}
